#include "pch.h"
#include "Direct2DManager.h"
#include "ImageManipulator.h"

#include <ppltasks.h>
#include <algorithm>

DeForm::ImageManipulator::ImageManipulator(IRandomAccessStream^ sourceStream)
{
	auto context = Direct2DManager::GetInstance()->GetD2DContext();
	auto wicFactory = Direct2DManager::GetInstance()->GetWICFactory();

	// Converter
	ComPtr<IWICFormatConverter> converter = GetConverter(sourceStream);

	// Create source effect
	Tools::Check(
		context->CreateEffect(CLSID_D2D1BitmapSource, &m_source)
		);

	Tools::Check(
		m_source->SetValue(D2D1_BITMAPSOURCE_PROP_WIC_BITMAP_SOURCE, converter.Get())
		);

	Tools::Check(
		m_source->SetValue(D2D1_PROPERTY_CACHED, TRUE)
		);

	// Size		
	converter->GetSize(&m_frameWidth, &m_frameHeight);
}

DeForm::ImageManipulator::~ImageManipulator()
{
	m_source = nullptr;

	DeleteSurfaceImageSource();
}

void DeForm::ImageManipulator::AddEffectDescription(IEffectDescription ^effectDescription)
{
	auto effect = Effect(effectDescription);
	m_effectDescriptions.push_back(effect);
}

void DeForm::ImageManipulator::RemoveEffectDescription(IEffectDescription ^effectDescription)
{
	int index;
	auto ite = m_effectDescriptions.begin();
	auto endIte = m_effectDescriptions.end();

	for (int index = 0; ite != endIte; index++, ite++)
	{
		Effect effect = *ite;
		if (effect.GetEffectDescription() == effectDescription)
		{
			m_effectDescriptions.erase(ite);
			return;
		}
	}
}

void DeForm::ImageManipulator::ClearEffectDescriptions()
{
	m_effectDescriptions.clear();
}

SurfaceImageSource^ DeForm::ImageManipulator::CreateSurfaceImageSource()
{
	if (m_surfaceImageSource == nullptr)
	{
		auto context = Direct2DManager::GetInstance()->GetD2DContext();

		m_surfaceImageSource = ref new SurfaceImageSource(m_frameWidth, m_frameHeight);
		IInspectable* inspectable = (IInspectable*) reinterpret_cast<IInspectable*>(m_surfaceImageSource);
		inspectable->QueryInterface(__uuidof(ISurfaceImageSourceNative), (void **)&m_nativeSurfaceImageSource);

		m_nativeSurfaceImageSource->SetDevice(Direct2DManager::GetInstance()->GetDXGIDevice().Get());
	}

	return m_surfaceImageSource;
}

void DeForm::ImageManipulator::DeleteSurfaceImageSource()
{
	if (m_surfaceImageSource == nullptr)
		return;

	m_nativeSurfaceImageSource->SetDevice(NULL);
	m_surfaceImageSource = nullptr;
	m_nativeSurfaceImageSource = nullptr;
}

void DeForm::ImageManipulator::Manipulate(bool updateSurfaceImageSource)
{	
	// Effets
	ComPtr<ID2D1Effect> previousD2DEffect = m_source;
	std::for_each(m_effectDescriptions.begin(), m_effectDescriptions.end(), [&previousD2DEffect](Effect effect)
	{
		previousD2DEffect = effect.Apply(previousD2DEffect);
	});

	previousD2DEffect->GetOutput(&m_d2dImage);

	if (updateSurfaceImageSource)
		UpdateSurfaceImageSource();
}

ComPtr<IWICFormatConverter> DeForm::ImageManipulator::GetConverter(IRandomAccessStream^ sourceStream)
{
	auto context = Direct2DManager::GetInstance()->GetD2DContext();
	auto wicFactory = Direct2DManager::GetInstance()->GetWICFactory();

	// Input Stream
	ComPtr<IStream> inputStream;
	CreateStreamOverRandomAccessStream(
		reinterpret_cast<IUnknown*>(sourceStream),
		IID_PPV_ARGS(&inputStream)
		);

	// WIC Decoder
	ComPtr<IWICBitmapDecoder> decoder;
	Tools::Check(
		wicFactory->CreateDecoderFromStream(
		inputStream.Get(),
		nullptr,
		WICDecodeMetadataCacheOnDemand,
		&decoder
		)
		);

	// Frame
	ComPtr<IWICBitmapFrameDecode> frame;
	Tools::Check(
		decoder->GetFrame(0, &frame)
		);

	// Convert to BGRA
	ComPtr<IWICFormatConverter> converter;
	Tools::Check(
		wicFactory->CreateFormatConverter(&converter)
		); 

	Tools::Check(
		converter->Initialize(
		frame.Get(),
		GUID_WICPixelFormat32bppPBGRA,
		WICBitmapDitherTypeNone,
		nullptr,
		0.0f,
		WICBitmapPaletteTypeCustom
		)
		);

	return converter;
}

void DeForm::ImageManipulator::SaveToStream(IRandomAccessStream^ destinationStream, CompressionMode compressionMode)
{
	auto wicFactory = Direct2DManager::GetInstance()->GetWICFactory();
	auto d2dDevice = Direct2DManager::GetInstance()->GetD2DDevice();

	// Output stream
	ComPtr<IStream> outputStream;
	Tools::Check(
		CreateStreamOverRandomAccessStream(destinationStream, IID_PPV_ARGS(&outputStream))
		);

	// Png encoder
	ComPtr<IWICBitmapEncoder> wicBitmapEncoder;
	Tools::Check(
		wicFactory->CreateEncoder(
		compressionMode == CompressionMode::PNG ? GUID_ContainerFormatPng : GUID_ContainerFormatJpeg,  
		nullptr,
		&wicBitmapEncoder
		)
		);

	Tools::Check(
		wicBitmapEncoder->Initialize(
		outputStream.Get(),
		WICBitmapEncoderNoCache
		)
		);

	// Frame encoder
	ComPtr<IWICBitmapFrameEncode> wicFrameEncode;
	Tools::Check(
		wicBitmapEncoder->CreateNewFrame(
		&wicFrameEncode,
		nullptr     
		)
		);

	Tools::Check(
		wicFrameEncode->Initialize(nullptr)
		);

	// Create IWICImageEncoder.
	ComPtr<IWICImageEncoder> imageEncoder;
	Tools::Check(
		wicFactory->CreateImageEncoder(
		d2dDevice.Get(),
		&imageEncoder
		)
		);

	Tools::Check(
		imageEncoder->WriteFrame(
		m_d2dImage.Get(),
		wicFrameEncode.Get(),
		nullptr 
		)
		);

	Tools::Check(
		wicFrameEncode->Commit()
		);

	Tools::Check(
		wicBitmapEncoder->Commit()
		);

	// Flush all memory buffers to the next-level storage object.
	Tools::Check(
		outputStream->Commit(STGC_DEFAULT)
		);
}

void DeForm::ImageManipulator::UpdateSurfaceImageSource()
{
	if (m_nativeSurfaceImageSource == nullptr)
		throw Platform::NullReferenceException::CreateException(1);

	auto context = Direct2DManager::GetInstance()->GetD2DContext();

	RECT updateRect;
	POINT offset;

	updateRect.left = 0;
	updateRect.top = 0;
	updateRect.right = m_frameWidth - 1;
	updateRect.bottom = m_frameHeight - 1;

	offset.x = 0;
	offset.y = 0;

	ComPtr<IDXGISurface> surface;
	Tools::Check(
		m_nativeSurfaceImageSource->BeginDraw(updateRect, &surface, &offset)
		);

	ComPtr<ID2D1Bitmap1> targetBitmap;
	D2D1_BITMAP_PROPERTIES1 bitmapProperties = {};
	bitmapProperties.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM;
	bitmapProperties.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
	bitmapProperties.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW;
	bitmapProperties.colorContext = nullptr;
	bitmapProperties.dpiX = Windows::Graphics::Display::DisplayProperties::LogicalDpi;
	bitmapProperties.dpiY = Windows::Graphics::Display::DisplayProperties::LogicalDpi;

	Tools::Check(
		context->CreateBitmapFromDxgiSurface(
		surface.Get(),
		&bitmapProperties,
		&targetBitmap
		)
		);

	context->SetTarget(targetBitmap.Get());

	context->BeginDraw();

	context->DrawImage(
		m_d2dImage.Get(),
		D2D1_INTERPOLATION_MODE_LINEAR,
		D2D1_COMPOSITE_MODE_SOURCE_COPY
		);

	context->EndDraw();

	m_nativeSurfaceImageSource->EndDraw();

	context->SetTarget(nullptr);
	targetBitmap = nullptr;
	surface = nullptr;
}
